package app

// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import (
	// "flag"
	"context"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"
	"time"

	// "strings"

	"github.com/google/uuid"
	// "github.com/pborman/uuid"
	"github.com/spf13/cobra"

	// "istio.io/istio/pilot/pkg/bootstrap"
	// "istio.io/istio/pilot/pkg/features"
	// "istio.io/istio/pilot/pkg/serviceregistry/provider"
	"istio.io/istio/pkg/cmd"
	// "istio.io/istio/pkg/config/constants"
	// "istio.io/pkg/collateral"
	// "istio.io/pkg/ctrlz"
	"ycontroller/controller"

	"istio.io/pkg/log"
	// "istio.io/pkg/version"

	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/leaderelection/resourcelock"

	// corev1 "k8s.io/client-go/kubernetes/typed/core/v1"

	"istio.io/istio/pkg/kube"
	// "k8s.io/client-go/util/homedir"
	// "k8s.io/client-go/tools/clientcmd"
	// "path/filepath"
	"istio.io/istio/pilot/pkg/serviceregistry/provider"
	// "istio.io/istio/pkg/config/mesh"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/leaderelection"
	// "k8s.io/klog/v2"
)

// URL schemes supported by the config store
type ConfigSourceAddressScheme string

const (
	dataDir = "/opt/ydir"

	// k8s:// - load in-cluster k8s controller
	// example k8s://
	Kubernetes ConfigSourceAddressScheme = "k8s"
)

var (
	serverArgs     = &YArgs{}
	loggingOptions = log.DefaultOptions()
)

type YArgs struct {
	Namespace          string
	ConfigmapName      string
	SelectorKey        string
	Kubeconfig         string
	Id                 string
	LeaseLockName      string
	LeaseLockNamespace string
}

// NewRootCommand returns the root cobra command of pilot-discovery.
func NewRootCommand() *cobra.Command {
	rootCmd := &cobra.Command{
		Use:          "ycontroller",
		Short:        "Istio Pilot.",
		Long:         "Istio Pilot provides fleet-wide traffic management capabilities in the Istio Service Mesh.",
		SilenceUsage: true,
		PreRunE: func(c *cobra.Command, args []string) error {
			cmd.AddFlags(c)
			return nil
		},
	}

	discoveryCmd := newDiscoveryCommand()
	addFlags(discoveryCmd)
	rootCmd.AddCommand(discoveryCmd)
	// rootCmd.AddCommand(version.CobraCommand())
	// rootCmd.AddCommand(collateral.CobraCommand(rootCmd, &doc.GenManHeader{
	// 	Title:   "Istio Pilot Discovery",
	// 	Section: "pilot-discovery CLI",
	// 	Manual:  "Istio Pilot Discovery",
	// }))
	// rootCmd.AddCommand(requestCmd)

	return rootCmd
}

func newDiscoveryCommand() *cobra.Command {
	return &cobra.Command{
		Use:   "discovery",
		Short: "Start Istio proxy discovery service.",
		Args:  cobra.ExactArgs(0),
		PreRunE: func(c *cobra.Command, args []string) error {
			// klog.InitFlags(nil)
			if err := log.Configure(loggingOptions); err != nil {
				return err
			}
			// if err := validateFlags(serverArgs); err != nil {
			// 	return err
			// }
			// if err := serverArgs.Complete(); err != nil {
			// 	return err
			// }
			return nil
		},
		RunE: func(c *cobra.Command, args []string) error {
			cmd.PrintFlags(c.Flags())

			// Create the stop channel for all of the servers.
			stop := make(chan struct{})

			// Create the server for the discovery service.
			client, err := InitKubeClient()
			if err != nil {
				log.Error("Failed creating kube client: %v", err)
				return nil
			}

			// use a Go context so we can tell the leaderelection code when we
			// want to step down
			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()

			if serverArgs.LeaseLockName == "" {
				log.Fatal("unable to get lease lock resource name (missing lease-lock-name flag).")
			}
			if serverArgs.LeaseLockNamespace == "" {
				log.Fatal("unable to get lease lock resource namespace (missing lease-lock-namespace flag).")
			}

			// listen for interrupts or the Linux SIGTERM signal and cancel
			// our context, which the leader election code will observe and
			// step down
			ch := make(chan os.Signal, 1)
			signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
			go func() {
				<-ch
				log.Info("Received termination, signaling shutdown")
				cancel()
			}()

			// we use the Lease lock type since edits to Leases are less common
			// and fewer objects in the cluster watch "all Leases".
			lock := &resourcelock.LeaseLock{
				LeaseMeta: metav1.ObjectMeta{
					Name:      serverArgs.LeaseLockName,
					Namespace: serverArgs.LeaseLockNamespace,
				},
				Client: client.CoordinationV1(),
				LockConfig: resourcelock.ResourceLockConfig{
					Identity: serverArgs.Id,
				},
			}

			// start the leader election code loop
			leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
				Lock: lock,
				// IMPORTANT: you MUST ensure that any code you have that
				// is protected by the lease must terminate **before**
				// you call cancel. Otherwise, you could have a background
				// loop still running and another process could
				// get elected before your background loop finished, violating
				// the stated goal of the lease.
				ReleaseOnCancel: true,
				LeaseDuration:   60 * time.Second,
				RenewDeadline:   15 * time.Second,
				RetryPeriod:     5 * time.Second,
				Callbacks: leaderelection.LeaderCallbacks{
					OnStartedLeading: func(ctx context.Context) {
						// we're notified when we start - this is where you would
						// usually put your code
						run(ctx, serverArgs, client, stop)
					},
					OnStoppedLeading: func() {
						// we can do cleanup here
						log.Infof("leader lost: %s", lock.LockConfig.Identity)
						os.Exit(0)
					},
					OnNewLeader: func(identity string) {
						// we're notified when new leader elected
						if identity == lock.LockConfig.Identity {
							// I just got the lock
							return
						}
						log.Infof("new leader elected: %s", identity)
					},
				},
			})

			return nil
		},
	}
}

func addFlags(c *cobra.Command) {

	c.PersistentFlags().StringVarP(&serverArgs.Namespace, "namespace", "n", "lab", "Get the Namespace for the hook ConfigMap")
	c.PersistentFlags().StringVarP(&serverArgs.ConfigmapName, "configmapName", "c", "start-tomcat", "Get the hook ConfigMap Name")
	c.PersistentFlags().StringVarP(&serverArgs.SelectorKey, "selectorKey", "l", "99bill-webhook/validate", "Get the LabelKey for the hook ConfigMap")
	c.PersistentFlags().StringVar(&serverArgs.Kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
	c.PersistentFlags().StringVar(&serverArgs.Id, "id", uuid.New().String(), "the holder identity name")
	c.PersistentFlags().StringVar(&serverArgs.LeaseLockName, "lease-lock-name", "ycontroller", "the lease lock resource name")
	c.PersistentFlags().StringVar(&serverArgs.LeaseLockNamespace, "lease-lock-namespace", "lab", "the lease lock resource namespace")
	// serverArgs = bootstrap.NewPilotArgs(func(p *bootstrap.PilotArgs) {
	// 	// Set Defaults
	// 	p.CtrlZOptions = ctrlz.DefaultOptions()
	// 	// TODO replace with mesh config?
	// 	p.InjectionOptions = bootstrap.InjectionOptions{
	// 		InjectionDirectory: "./var/lib/istio/inject",
	// 	}
	// })

	// // Process commandline args.
	// c.PersistentFlags().StringSliceVar(&serverArgs.RegistryOptions.Registries, "registries",
	// 	[]string{string(provider.Kubernetes)},
	// 	fmt.Sprintf("Comma separated list of platform service registries to read from (choose one or more from {%s, %s})",
	// 		provider.Kubernetes, provider.Mock))
	// c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.ClusterRegistriesNamespace, "clusterRegistriesNamespace",
	// 	serverArgs.RegistryOptions.ClusterRegistriesNamespace, "Namespace for ConfigMap which stores clusters configs")
	// c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeConfig, "kubeconfig", "",
	// 	"Use a Kubernetes configuration file instead of in-cluster configuration")
	// c.PersistentFlags().StringVar(&serverArgs.MeshConfigFile, "meshConfig", "./etc/istio/config/mesh",
	// 	"File name for Istio mesh configuration. If not specified, a default mesh will be used.")
	// c.PersistentFlags().StringVar(&serverArgs.NetworksConfigFile, "networksConfig", "./etc/istio/config/meshNetworks",
	// 	"File name for Istio mesh networks configuration. If not specified, a default mesh networks will be used.")
	// c.PersistentFlags().StringVarP(&serverArgs.Namespace, "namespace", "n", bootstrap.PodNamespace,
	// 	"Select a namespace where the controller resides. If not set, uses ${POD_NAMESPACE} environment variable")
	// c.PersistentFlags().StringSliceVar(&serverArgs.Plugins, "plugins", bootstrap.DefaultPlugins,
	// 	"comma separated list of networking plugins to enable")
	// c.PersistentFlags().DurationVar(&serverArgs.ShutdownDuration, "shutdownDuration", 10*time.Second,
	// 	"Duration the discovery server needs to terminate gracefully")

	// // RegistryOptions Controller options
	// c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.FileDir, "configDir", "",
	// 	"Directory to watch for updates to config yaml files. If specified, the files will be used as the source of config, rather than a CRD client.")
	// c.PersistentFlags().DurationVar(&serverArgs.RegistryOptions.KubeOptions.ResyncPeriod, "resync", 60*time.Second,
	// 	"Controller resync interval")
	// c.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeOptions.DomainSuffix, "domain", constants.DefaultKubernetesDomain,
	// 	"DNS domain suffix")
	// c.PersistentFlags().StringVar((*string)(&serverArgs.RegistryOptions.KubeOptions.ClusterID), "clusterID", features.ClusterName,
	// 	"The ID of the cluster that this Istiod instance resides")

	// // using address, so it can be configured as localhost:.. (possibly UDS in future)
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.HTTPAddr, "httpAddr", ":8080",
	// 	"Discovery service HTTP address")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.HTTPSAddr, "httpsAddr", ":15017",
	// 	"Injection and validation service HTTPS address")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.GRPCAddr, "grpcAddr", ":15010",
	// 	"Discovery service gRPC address")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.SecureGRPCAddr, "secureGRPCAddr", ":15012",
	// 	"Discovery service secured gRPC address")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.MonitoringAddr, "monitoringAddr", ":15014",
	// 	"HTTP address to use for pilot's self-monitoring information")
	// c.PersistentFlags().BoolVar(&serverArgs.ServerOptions.EnableProfiling, "profile", true,
	// 	"Enable profiling via web interface host:port/debug/pprof")

	// // Use TLS certificates if provided.
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.TLSOptions.CaCertFile, "caCertFile", "",
	// 	"File containing the x509 Server CA Certificate")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.TLSOptions.CertFile, "tlsCertFile", "",
	// 	"File containing the x509 Server Certificate")
	// c.PersistentFlags().StringVar(&serverArgs.ServerOptions.TLSOptions.KeyFile, "tlsKeyFile", "",
	// 	"File containing the x509 private key matching --tlsCertFile")
	// // c.PersistentFlags().StringSliceVar(&serverArgs.ServerOptions.TLSOptions.TLSCipherSuites, "tls-cipher-suites", nil,
	// // 	"Comma-separated list of cipher suites for istiod TLS server. "+
	// // 		"If omitted, the default Go cipher suites will be used. \n"+
	// // 		"Preferred values: "+strings.Join(secureTLSCipherNames(), ", ")+". \n"+
	// // 		"Insecure values: "+strings.Join(insecureTLSCipherNames(), ", ")+".")

	// c.PersistentFlags().Float32Var(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIQPS, "kubernetesApiQPS", 80.0,
	// 	"Maximum QPS when communicating with the kubernetes API")

	// c.PersistentFlags().IntVar(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIBurst, "kubernetesApiBurst", 160,
	// 	"Maximum burst for throttle when communicating with the kubernetes API")

	// // Attach the Istio logging options to the command.
	// loggingOptions.AttachCobraFlags(c)

	// // Attach the Istio Ctrlz options to the command.
	// serverArgs.CtrlZOptions.AttachCobraFlags(c)

	// // Attach the Istio Keepalive options to the command.
	// serverArgs.KeepaliveOptions.AttachCobraFlags(c)
}

func FetchData() map[string]string {

	var datas map[string]string = make(map[string]string)

	fileNames, err := GetAllFile(dataDir)
	if err != nil {
		return nil
	}

	if len(fileNames) == 0 {
		return nil
	}

	for _, fileName := range fileNames {
		data, err := ioutil.ReadFile(dataDir + "/" + fileName)
		if err != nil {
			log.Errorf("Read file %s failed", fileName)
			return nil
		}
		datas[fileName] = string(data)
	}

	return datas

}

func GetAllFile(path string) ([]string, error) {
	var s []string

	rd, err := ioutil.ReadDir(path)
	if err != nil {
		return s, err
	}

	for _, fi := range rd {
		if !fi.IsDir() {
			if fi.Name()[0] == []byte(".")[0] {
				continue
			}
			// fullName := path + "/" + fi.Name()
			// s = append(s, fullName)
			s = append(s, fi.Name())
		}
	}

	return s, nil
}

// initKubeClient creates the k8s client if running in an k8s environment.
// This is determined by the presence of a kube registry, which
// uses in-context k8s, or a config source of type k8s.

// func initKubeClient() *kubernetes.Clientset {
// 	// creates the in-cluster config
// 	config, err := rest.InClusterConfig()
// 	if err != nil {
// 		panic(err.Error())
// 	}
// 	// creates the clientset
// 	clientset, err := kubernetes.NewForConfig(config)
// 	if err != nil {
// 		panic(err.Error())
// 	}

// 	return clientset
// }

// func initIstioKubeClient() kube.Client {
// 	// var kubeconfig *string
// 	// if home := homedir.HomeDir(); home != "" {
// 	// 	kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
// 	// } else {
// 	// 	kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
// 	// }
// 	// flag.Parse()

// 	// // use the current context in kubeconfig
// 	// config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
// 	// if err != nil {
// 	// 	panic(err.Error())
// 	// }

// 	// // create the clientset
// 	// clientset, err := kubernetes.NewForConfig(config)
// 	// if err != nil {
// 	// 	panic(err.Error())
// 	// }

// 	client, err := kube.NewClient(&clientcmd.DefaultClientConfig)
// 	if err != nil {
// 		panic(err.Error())
// 	}

// 	return client
// }

// initKubeClient creates the k8s client if running in an k8s environment.
// This is determined by the presence of a kube registry, which
// uses in-context k8s, or a config source of type k8s.
func InitKubeClient() (kube.Client, error) {
	var kubeClient kube.Client

	if hasKubeRegistry([]string{"Kubernetes"}) {
		// Used by validation
		kubeRestConfig, err := rest.InClusterConfig()
		if err != nil {
			// return fmt.Errorf("failed creating kube config: %v", err)
			log.Error("failed creating kube config: %v", err)
			return nil, err
		}

		kubeClient, err = kube.NewClient(kube.NewClientConfigForRestConfig(kubeRestConfig))
		if err != nil {
			// return fmt.Errorf("failed creating kube client: %v", err)
			log.Error("failed creating kube client: %v", err)
			return nil, err
		}
	}

	return kubeClient, nil
}

func hasKubeRegistry(registries []string) bool {
	for _, r := range registries {
		if provider.ID(r) == provider.Kubernetes {
			return true
		}
	}
	return false
}

func Wait(stop chan struct{}) {
	<-stop
}

func run(ctx context.Context, args *YArgs, client kube.Client, stop chan struct{}) {
	var info map[string]string = make(map[string]string)
	info["namespace"] = args.Namespace
	info["selectorKey"] = args.SelectorKey
	info["configmapName"] = args.ConfigmapName

	firstInitData, err := client.CoreV1().ConfigMaps(args.Namespace).Get(ctx, args.ConfigmapName, metav1.GetOptions{})
	if err != nil {
		log.Errorf("First Init Data to controller failed: %v", err)
		return
	}

	yServer := controller.NewNamespaceController(info, firstInitData.Data, client)

	// discoveryServer, err := bootstrap.NewServer(serverArgs)
	// if err != nil {
	// 	return fmt.Errorf("failed to create discovery service: %v", err)
	// }

	// Start informers again. This fixes the case where informers for namespace do not start,
	// as we create them only after acquiring the leader lock
	client.RunAndWait(stop)

	// Start the server
	yServer.Run(stop)

	cmd.WaitSignal(stop)
	// Wait until we shut down. In theory this could block forever; in practice we will get
	// forcibly shut down after 30s in Kubernetes.
	// yServer.WaitUntilCompletion()
	Wait(stop)
}
